﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using MyFloridaHouse.ServiceConn.DistrBldrSvcRef;
using MyFloridaHouse.libSvOpenDistricts.MapShapes;

namespace MyFloridaHouse.libSvOpenDistricts.Criteria
{
    public class CriteriaDataset
    {

        public static DataDictionaryIDList GetUniqueDictionaryIDListFromDatasetName(string criteriaDatasetName)
        {
            List<CriteriaDatasetDTO> dataset = CriteriaDatasetManager.GetCriteriaDataset(criteriaDatasetName).ToList();
			List<SourceDataSpecificationDTO> sourceDataList = CriteriaDataset.GetUniqueSourceDataSpecificationFromCriteriaDataset(dataset);
            return GetUniqueDictionaryIDListFromSourceDataSpecificationList(sourceDataList);
        }

		public static DataDictionaryIDList GetUniqueDictionaryIDListFromSourceDataSpecificationList(List<SourceDataSpecificationDTO> sourceDataList)
        {

            DataDictionaryIDList result = new DataDictionaryIDList();

            //Must convert array to two strings to send to database
            List<string> dictionaryIDList = new List<string>();
            List<string> sourceLayerIDList = new List<string>();

            foreach (var sourceData in sourceDataList)
            {
                dictionaryIDList.Add(string.Join(",", sourceData.dictionaryIDList));
                foreach (var dictID in sourceData.dictionaryIDList)
                {
                    sourceLayerIDList.Add(sourceData.sourceLayerID.ToString());
                }
            }
            result.layerIDList = string.Join(",", sourceLayerIDList);
            result.dictionaryIDList = string.Join(",", dictionaryIDList);

            return result;
        }

        public static List<SourceDataSpecificationDTO> GetUniqueSourceDataSpecificationFromCriteriaDataset(List<CriteriaDatasetDTO> criteriaDataset)
        {
            List<CriteriaDictionaryDTO> criteriaDictionaryItemList = new List<CriteriaDictionaryDTO>();

            foreach (var criteria in criteriaDataset)
            {
                foreach (var criteriaDictionaryItem in criteria.dictionaryList)
                {
                    criteriaDictionaryItemList.Add(criteriaDictionaryItem);
                }

            }
            return GetUniqueSourceDataSpecificationFromCriteriaDictionaryList(criteriaDictionaryItemList);
        }

        public static List<SourceDataSpecificationDTO> GetUniqueSourceDataSpecificationFromCriteriaDictionaryList(List<CriteriaDictionaryDTO> criteriaDictionaryList)
        {
			List<SourceDataSpecificationDTO> tempSourceDataSpecificationList = new List<SourceDataSpecificationDTO>();

            var uniqueDataSourceLayerIDQuery = (from criteria in criteriaDictionaryList
                                                select criteria.layerID).Distinct();
            foreach (var layerID in uniqueDataSourceLayerIDQuery)
            {
                SourceDataSpecificationDTO x = new SourceDataSpecificationDTO();

                var uniqueDataSourceDictionaryIDQuery = (from criteria in criteriaDictionaryList
                                                         where criteria.layerID == layerID
                                                         select criteria.dictionaryID).Distinct();
                x.sourceLayerID = layerID;
                x.dictionaryIDList = uniqueDataSourceDictionaryIDQuery.ToList();
                tempSourceDataSpecificationList.Add(x);
            }

            return tempSourceDataSpecificationList;
        }

        public static bool MeetsQueryCriteria(List<RegionDataDTO> regionData, ObservableCollection<CriteriaDatasetDTO> datasetMemberList)
        {
            List<bool> resultList = new List<bool>();
            foreach (var criteria in datasetMemberList)
            {
                if (criteria.isQueryCriteria)
                {
                    int regionComputedDataValue = 0;
                    resultList.Add(EvaluateCriteria(regionData, criteria, true, out regionComputedDataValue));
                }
            }

            foreach (bool res in resultList)
            {
                if (res != true) return false;
            }
            return true;
        }

        public static bool EvaluateCriteria(List<RegionDataDTO> regionData, CriteriaDatasetDTO criteria, bool includeOnlyIfMeetsCriteria, out int regionComputedDataValue)
        {
            // criteria type: plain number comparison
            switch (criteria.criteriaType)
            {
                case enumCriteriaType.Total:
                    return (EvaluateCriteriaType_Total(regionData, criteria, includeOnlyIfMeetsCriteria, out regionComputedDataValue));

                case enumCriteriaType.TotalDifference:
                    return (EvaluateCriteriaType_TotalDifference(regionData, criteria, includeOnlyIfMeetsCriteria, out regionComputedDataValue));

                case enumCriteriaType.TotalDifferenceRelativeMagnitude:
                    return (EvaluateCriteriaType_TotalDifferenceRelativeMagnitude(regionData, criteria, includeOnlyIfMeetsCriteria, out regionComputedDataValue));

                case enumCriteriaType.Percent:
                    return (EvaluateCriteriaType_Percent(regionData, criteria, includeOnlyIfMeetsCriteria, out  regionComputedDataValue));

                case enumCriteriaType.PercentDifference:
                    return (EvaluateCriteriaType_PercentDifference(regionData, criteria, includeOnlyIfMeetsCriteria, out  regionComputedDataValue));

                default:
                    throw new Exception("invalid enumCriteriaType [" + criteria.criteriaType.ToString() + "]");
            }
        }

        public static bool EvaluateIntCriteria(List<RegionDataDTO> regionData, CriteriaDatasetDTO criteria, bool includeOnlyIfMeetsCriteria, out int regionComputedDataValue)
        {
            switch (criteria.criteriaType)
            {
                case enumCriteriaType.Total:
                    return (EvaluateCriteriaType_Total(regionData, criteria, includeOnlyIfMeetsCriteria, out regionComputedDataValue));

                case enumCriteriaType.TotalDifference:
                    return (EvaluateCriteriaType_TotalDifference(regionData, criteria, includeOnlyIfMeetsCriteria, out regionComputedDataValue));

                case enumCriteriaType.TotalDifferenceRelativeMagnitude:
                    return (EvaluateCriteriaType_TotalDifferenceRelativeMagnitude(regionData, criteria, includeOnlyIfMeetsCriteria, out regionComputedDataValue));

                default:
                    throw new Exception("invalid enumCriteriaType for integer evaluation[" + criteria.criteriaType.ToString() + "]");
            }
        }

        public static bool EvaluateFloatCriteria(List<RegionDataDTO> regionData, CriteriaDatasetDTO criteria, bool includeOnlyIfMeetsCriteria, out float regionComputedDataValueFloat)
        {
            // criteria type: plain number comparison
            switch (criteria.criteriaType)
            {
                case enumCriteriaType.Percent:
                    return (EvaluateCriteriaType_PercentFloat(regionData, criteria, includeOnlyIfMeetsCriteria, out  regionComputedDataValueFloat));

                case enumCriteriaType.PercentDifference:
                    return (EvaluateCriteriaType_PercentDifferenceFloat(regionData, criteria, includeOnlyIfMeetsCriteria, out  regionComputedDataValueFloat));

                default:
                    throw new Exception("invalid enumCriteriaType for floating point evaluation [" + criteria.criteriaType.ToString() + "]");
            }
        }

        private static bool EvaluateCriteriaType_Total(List<RegionDataDTO> regionData, CriteriaDatasetDTO criteria, bool includeOnlyIfMeetsCriteria, out int regionComputedDataValue)
        {
            regionComputedDataValue = 0;

            List<QueryCriteriaData> criteriaDataList;
            if (GetDataForCriteria(regionData, criteria, 1, out criteriaDataList))
            {
                regionComputedDataValue = criteriaDataList[0].data;

                if (includeOnlyIfMeetsCriteria)
                {
                    if ((regionComputedDataValue >= criteria.queryMin) & (regionComputedDataValue <= criteria.queryMax))
                    {
                        return true;
                    }
                }
                else
                {
                    return true;
                }
            }
            return false;
        }

        private static bool EvaluateCriteriaType_TotalDifference(List<RegionDataDTO> regionData, CriteriaDatasetDTO criteria, bool includeOnlyIfMeetsCriteria, out int regionComputedDataValue)
        {
            regionComputedDataValue = 0;

            List<QueryCriteriaData> criteriaDataList;
            if (GetDataForCriteria(regionData, criteria, 2, out criteriaDataList))
            {
                int data1 = criteriaDataList[0].data;
                int data2 = criteriaDataList[1].data;
                regionComputedDataValue = data1 - data2;

                if (includeOnlyIfMeetsCriteria)
                {
                    if ((regionComputedDataValue >= criteria.queryMin) & (regionComputedDataValue <= criteria.queryMax))
                    {
                        return true;
                    }
                }
                else
                {
                    return true;
                }
            }
            return false;
        }

        private static bool EvaluateCriteriaType_TotalDifferenceRelativeMagnitude(List<RegionDataDTO> regionData, CriteriaDatasetDTO criteria, bool includeOnlyIfMeetsCriteria, out int regionComputedDataValue)
        {
            regionComputedDataValue = 0;

            List<QueryCriteriaData> criteriaDataList;
            if (GetDataForCriteria(regionData, criteria, 2, out criteriaDataList))
            {
                int data1 = criteriaDataList[0].data;
                int data2 = criteriaDataList[1].data;
                regionComputedDataValue = CalculateRelativeMagnitude(data1, data2);

                if (includeOnlyIfMeetsCriteria)
                {
                    if ((regionComputedDataValue >= criteria.queryMin) & (regionComputedDataValue <= criteria.queryMax))
                    {
                        return true;
                    }
                }
                else
                {
                    return true;
                }
            }
            return false;
        }

        private static bool EvaluateCriteriaType_Percent(List<RegionDataDTO> regionData, CriteriaDatasetDTO criteria, bool includeOnlyIfMeetsCriteria, out int regionComputedDataValue)
        {
            // wrap floating point based method as integer method
            float regionComputedDataValueFloat;
            bool result = EvaluateCriteriaType_PercentFloat(regionData, criteria, includeOnlyIfMeetsCriteria, out regionComputedDataValueFloat);
            regionComputedDataValue = (int)regionComputedDataValueFloat;
            return result;
        }

        private static bool EvaluateCriteriaType_PercentFloat(List<RegionDataDTO> regionData, CriteriaDatasetDTO criteria, bool includeOnlyIfMeetsCriteria, out float regionComputedDataValueFloat)
        {
            regionComputedDataValueFloat = 0.0f;

            List<QueryCriteriaData> criteriaDataList;
            if (GetDataForCriteria(regionData, criteria, 2, out criteriaDataList))
            {
                int data1 = criteriaDataList[0].data;
                int data2 = criteriaDataList[1].data;
                if (data2 == 0)
                {
                    regionComputedDataValueFloat = 0.0f;
                }
                else
                {
                    regionComputedDataValueFloat = (((float)data1 / (float)data2) * 100.0f);
                }


                if (includeOnlyIfMeetsCriteria)
                {
                    if ((regionComputedDataValueFloat >= criteria.queryMin) & (regionComputedDataValueFloat <= criteria.queryMax))
                    {
                        return true;
                    }
                }
                else
                {
                    return true;
                }
            }
            return false;
        }

        private static bool EvaluateCriteriaType_PercentDifference(List<RegionDataDTO> regionData, CriteriaDatasetDTO criteria, bool includeOnlyIfMeetsCriteria, out int regionComputedDataValue)
        {
            // wrap floating point based method as integer method
            float regionComputedDataValueFloat;
            bool result = EvaluateCriteriaType_PercentDifferenceFloat( regionData, criteria, includeOnlyIfMeetsCriteria, out regionComputedDataValueFloat);
            regionComputedDataValue = (int)regionComputedDataValueFloat;
            return result;
        }

        private static bool EvaluateCriteriaType_PercentDifferenceFloat(List<RegionDataDTO> regionData, CriteriaDatasetDTO criteria, bool includeOnlyIfMeetsCriteria, out float regionComputedDataValueFloat)
        {
            regionComputedDataValueFloat = 0.0f;

            List<QueryCriteriaData> criteriaDataList;
            if (GetDataForCriteria(regionData, criteria, 4, out criteriaDataList))
            {
                int data1 = criteriaDataList[0].data;
                int data2 = criteriaDataList[1].data;
                if (data2 == 0)
                {
                    regionComputedDataValueFloat = 0.0f;
                }
                else
                {
                    float pct1 = (float)data1 / (float)data2 * 100.0f;

                    int data3 = criteriaDataList[2].data;
                    int data4 = criteriaDataList[3].data;
                    if (data4 == 0)
                    {
                        regionComputedDataValueFloat = 0.0f;
                    }
                    else
                    {
                        float pct2 = (float)data3 / (float)data4 * 100.0f;

                        if (pct2 == 0)
                        {
                            regionComputedDataValueFloat = 00.0f;
                        }
                        else
                        {
                            regionComputedDataValueFloat = pct1 - pct2;
                        }
                    }
                }

                if (includeOnlyIfMeetsCriteria)
                {
                    if ((regionComputedDataValueFloat >= criteria.queryMin) & (regionComputedDataValueFloat <= criteria.queryMax))
                    {
                        return true;
                    }
                }
                else
                {
                    return true;
                }
            }
            return false;

        }

        private static int CalculateRelativeMagnitude(int data1, int data2)
        {
            // returns a value from 0 to 100 for display on the color scale
            int relativeMagnitude = 50;

            if(data1 == 0) 
            {
    	        relativeMagnitude = 0; 
    	    }
            else if (data2 == 0)
            {
                relativeMagnitude = 100; //deep blue
            }

            else if (data1 == data2)
            {
                relativeMagnitude = 50; //deep blue
            }

            else if(data1 < data2) 
            {
                relativeMagnitude = (int)(((double)data1 / (double)data2)* 50.0) ; 
	        }	
            else if(data1 > data2) 
            {
                relativeMagnitude = 50 + (int)((double)(1.0 - (double)data2 / (double)data1) * 50.0); 
	        }
            return relativeMagnitude;
        }


        private static bool GetDataForCriteria(List<RegionDataDTO> regionData, CriteriaDatasetDTO criteria, int expectedDataCount, out List<QueryCriteriaData> criteriaDataList)
        {
            // first identify all data items needed
            criteriaDataList =
                    (from crit in criteria.dictionaryList
                     join data in regionData
                     on
                         new { crit.layerID, crit.dictionaryID }
                         equals new { layerID = data.sourceLayerID, data.dictionaryID }
                     select new QueryCriteriaData(data.sourceLayerID, data.regionID, data.dictionaryID, data.dataValue)).ToList();

            //todo: GetDataForCriteria:  Possible issue, this will not detect the case where a criteria is missing a data value and another criteria has extra data value (unlikely case, but possible)

            if (criteriaDataList.Count != expectedDataCount)
            {
                // GetDataForCriteria: Need to log the layer and region that causes a "incorrect number of data values" error
                //Log.HandleInternalError("GetDataForCriteria: criteria [" + criteria.criteriaName + "] :   has incorrect number of data values, should  have [" + expectedDataCount.ToString() + "] ");

                return (false);
            }
            else
            {
                if (!ConvertDataToCriteriaData(criteriaDataList))
                    return false;
            }
            return true;
        }


        private static bool ConvertDataToCriteriaData(List<QueryCriteriaData> criteriaDataList)
        {
            foreach (var dataElement in criteriaDataList)
            {
                // convert data values to integer

                if (!Int32.TryParse(dataElement.dataValue, out dataElement.data))
                {
                    // todo: Need to error log this, not show it to the user
                    //throw new Exception("layer [" + dataElement.layerID + "] : region [" + dataElement.layerID + "] : dictionaryID [" + dataElement.dictionaryID + "] : dataValue [" + dataElement.dataValue + "] : has a non integer value ");
                    return false;
                }
            }
            return true;
        }

    }
}
